/*
 * Copyright (C) 2023, KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <notification-client.h>
#include <applications-settings.h>
#include <notification-global-settings.h>

#include <QHash>
#include <QVector>
#include <QTimer>
#include <QDebug>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusPendingReply>

#include <mutex>

#include "notification-model.h"

//声音播放音效dbus
#define UKUI_SOUNDTHEME_NAME          "org.ukui.sound.theme.player"
#define UKUI_SOUNDTHEME_PATH          "/org/ukui/sound/theme/player"
#define UKUI_SOUNDTHEME_INTERFACE     "org.ukui.sound.theme.player"
#define PLAY_SOUND_METHOD             "playAlertSound"


std::once_flag once_flag;

namespace UkuiNotification {

QHash<int, QByteArray> NotificationItem::roles()
{
    static QHash<int, QByteArray> roles;

    std::call_once(once_flag, [] {
        roles.insert(Id, "id");
        roles.insert(Display, "display");
        roles.insert(AppName, "appName");
        roles.insert(AppIconName, "appIconName");
        roles.insert(Icon, "icon");
        roles.insert(Summary, "summary");
        roles.insert(Body, "body");
        roles.insert(Category, "category");
        roles.insert(Image, "image");
        roles.insert(CreateTime, "createTime");
        roles.insert(Actions, "actions");
        roles.insert(ActionState, "actionState");
        roles.insert(HasDefaultAction, "hasDefaultAction");
        roles.insert(EnableActionIcons, "enableActionIcons");
        roles.insert(SoundFile, "soundFile");
        roles.insert(SuppressSound, "suppressSound");
        roles.insert(Resident, "resident");
        roles.insert(Transient, "transient");
        roles.insert(Urgency, "urgency");
        roles.insert(Timout, "timout");
        roles.insert(NoFold, "noFold");
        roles.insert(PopupTimeout, "popupTimeout");
        roles.insert(IsStored, "isStored");
        roles.insert(IsExpired, "isExpired");
        roles.insert(GroupName, "groupName");
        roles.insert(GroupCount, "groupCount");
        roles.insert(GroupIndex, "groupIndex");
        roles.insert(GroupIsExpand, "groupIsExpand");
    });

    return roles;
}

NotificationItem::NotificationItem(const PopupNotification& notification)
{
    setData(notification);
}

uint NotificationItem::id() const
{
    return data.id();
}

void NotificationItem::setData(const PopupNotification &notification)
{
    data = notification;
    updateActions();
}

void NotificationItem::updateActions()
{
    actions.clear();

    ActionList actionList(data.actions());
    QStringList actionState(data.actionState());

    for (int i = 0; i < actionList.size(); ++i) {
        NotificationAction action;

        action.setEnable(true);
        action.setDefault(data.hasDefaultAction() && actionList[i].first == "default");
        action.setIndex(i);
        action.setIcon(data.enableActionIcons() ? actionList[i].second : "");
        action.setName(actionList[i].second);
        action.setAction(actionList[i].first);
        action.setState(i < actionState.size() ? actionState[i] : "");

        actions.append(action);
    }
}

// ===== Private ======//
class NotificationModelPrivate
{
public:
    virtual ~NotificationModelPrivate();
    NotificationGlobalSettings *globalSettings {nullptr};
    // 消息客户端，收取消息
    NotificationClient *client {nullptr};

    // 全部消息记录
//    QVector<PopupNotification> notifications;
    QVector<NotificationItem> notifications;
    // 计时器，控制每条弹窗消息定时收起
    QHash<uint, QTimer*> notificationTimers;

    // 即将删除的消息Id列表
    QSet<uint> pendingRemovalIds;
    // 计时器，在计时器结束后，删除消息
    QTimer *pendingRemovalTimer {nullptr};

public:
    void deleteTimer(const uint &id);
    void prepareToDeleteNotification(const uint &id);
};

NotificationModelPrivate::~NotificationModelPrivate()
{
    qDeleteAll(notificationTimers);
    notificationTimers.clear();
}

void NotificationModelPrivate::deleteTimer(const uint &id)
{
    delete notificationTimers.take(id);
}

void NotificationModelPrivate::prepareToDeleteNotification(const uint &id)
{
    // 删除定时器
    deleteTimer(id);

    // 重启计时器，删除通知
    pendingRemovalTimer->stop();
    pendingRemovalIds.insert(id);
    pendingRemovalTimer->start();
}

// ===== M ====== //
NotificationModel *NotificationModel::instance()
{
    static NotificationModel instance;
    return &instance;
}

NotificationModel::NotificationModel(QObject *parent) : QAbstractListModel(parent), d(new NotificationModelPrivate)
{
    qRegisterMetaType<NotificationAction>("NotificationAction");
    init();
}

void NotificationModel::init()
{
    // 初始化timer
    d->pendingRemovalTimer = new QTimer(this);
    d->pendingRemovalTimer->setSingleShot(true);
    d->pendingRemovalTimer->setInterval(50);
    connect(d->pendingRemovalTimer, &QTimer::timeout, this, &NotificationModel::removeNotifications);

    // 通知全局设置
    d->globalSettings = new NotificationGlobalSettings(this);

    d->client = new NotificationClient(this);

    connect(d->client, &NotificationClient::newNotification, this, &NotificationModel::onNotificationReceived);
    connect(d->client, &NotificationClient::notificationClosed, this, &NotificationModel::onNotificationClosed);

    if (!d->client->registerClient()) {
        qWarning() << "register client failed.";
    }
}

int NotificationModel::findNotificationIndex(uint id) const
{
    auto it = std::find_if(d->notifications.constBegin(), d->notifications.constEnd(), [&id] (const NotificationItem &n) {
        return n.id() == id;
    });

    if (it == d->notifications.constEnd()) {
        return -1;
    }

    return std::distance(d->notifications.constBegin(), it);
}

void NotificationModel::initNewNotification(NotificationItem &item, SingleApplicationSettings *appSetting)
{
//        switch (appSetting->popupStyle()) {
//            case SettingsProperty::TransientPopup: {
//                if (item.data.popupTimeout() < 0) {
//                    item.flag = NotificationItem::ResidentPopup;
//                    break;
//                }
//
//                if (item.data.popupTimeout() > 0) {
//                    item.flag = NotificationItem::TransientPopup;
//                    break;
//                }
//
//                item.flag = NotificationItem::Stored;
//                break;
//            }
//            case SettingsProperty::ResidentPopup:
//                item.flag = NotificationItem::ResidentPopup;
//                break;
//            case SettingsProperty::NoPopup:
//            default:
//                item.flag = NotificationItem::Stored;
//        }

    if (item.data.popupTimeout() < 0) {
        item.flag = NotificationItem::ResidentPopup;

    } else if (item.data.popupTimeout() > 0) {
        item.flag = m_sidebarVisable ? NotificationItem::Stored : NotificationItem::TransientPopup;

    } else {
        item.flag = NotificationItem::Stored;
    }

    if (appSetting->allowSound() && !item.data.suppressSound()) {
        // TODO: 一段时间内只响一次
        // 响铃 ding ~
        if (item.data.soundFile().isEmpty()) {
            playSound(item.data.soundName().isEmpty() ? "notification-general" : item.data.soundName());

        } else {
            playSound("notification-general");
            // FIXME: 实现声音文件播放
//                playSoundFile(item.data.soundFile());
        }
    }
}

void NotificationModel::onNotificationReceived(const PopupNotification &notification)
{
    if (!d->globalSettings->receiveNotificationsFromApps()) {
        return;
    }

    SingleApplicationSettings *appSetting = ApplicationsSettings::self()->creatSettings(notification);
    if (!appSetting->allowNotify()) {
        return;
    }

    int index = findNotificationIndex(notification.id());
    if (index >= 0) {
        // 已经存在消息，更新消息
        updateNotification(index, notification);
        return;
    }

    if (d->notifications.size() >= 500) {
        beginRemoveRows(QModelIndex(), 0, 0);
        d->deleteTimer(d->notifications.at(0).id());
        d->notifications.removeAt(0);
        endRemoveRows();

//        int count = d->notifications.size() - 500;
//        for (int i = count - 1; i >= 0; --i) {
//            beginRemoveRows(QModelIndex(), i, i);
//            d->notifications.removeAt(i);
//            endRemoveRows();
//        }
    }

    // TODO 查询应用设置，检查系统设置，响铃

    /*
     *   bool allowNotify() const;  y
     *   bool allowSound() const ;  y
     *   bool showContentOnLockScreen() const ;  n
     *   bool showNotificationOnLockScreen() const;  n
     *   SettingsProperty::Property popupStyle() const;  y
     */

    NotificationItem item(notification);

    // 勿扰模式
    if (d->globalSettings->isDND()) {
        if (d->globalSettings->notifyAlarmWhileDND() && item.data.desktopEntry().contains("ukui-clock.desktop")) {
            initNewNotification(item, appSetting);
        } else {
            item.flag = NotificationItem::Stored;
        }

    } else {
        initNewNotification(item, appSetting);
    }

    int timeout = item.data.timeout();
    if (item.flag & NotificationItem::TransientPopup) {
        // 弹窗
        bool destroy = true;
        if (timeout <= 0 || (timeout > item.data.popupTimeout())) {
            destroy = false;
            timeout = item.data.popupTimeout();
        }
        updateNotificationTimer(item.id(), timeout, destroy);

    } else {
        // 非弹窗
        if (timeout > 0 && !item.data.resident()) {
            updateNotificationTimer(item.id(), timeout, true);
        }
    }

    // 插入数据
    beginInsertRows(QModelIndex(), d->notifications.size(), d->notifications.size());
    d->notifications.append(std::move(item));
    endInsertRows();
}

void NotificationModel::playSound(const QString& soundName)
{
    QDBusMessage message = QDBusMessage::createMethodCall(UKUI_SOUNDTHEME_NAME, UKUI_SOUNDTHEME_PATH, UKUI_SOUNDTHEME_INTERFACE, PLAY_SOUND_METHOD);
    message << soundName;

    auto watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [] (QDBusPendingCallWatcher *self) {
        if (self) {
            QDBusPendingReply<QString, QByteArray> reply = *self;
            if (reply.isError()) {
                qWarning() << "playSound error:" << reply.error().message();
            }

            self->deleteLater();
        }
    });
}

void NotificationModel::playSoundFile(const QString &soundFile)
{
    // TODO: 播放自定义声音文件
}

void
NotificationModel::onNotificationClosed(uint id, UkuiNotification::NotificationCloseReason::CloseReason closeReason)
{
    int index = findNotificationIndex(id);
    if (index < 0) {
        return;
    }

    d->prepareToDeleteNotification(id);
    // 插入数据
    // beginRemoveRows(QModelIndex(), index, index);
    // d->notifications.removeAt(index);
    // endRemoveRows();
}

void NotificationModel::updateNotification(int row, const PopupNotification &notification)
{
    NotificationItem &item = d->notifications[row];
    item.setData(notification);

    if (d->pendingRemovalIds.contains(item.id())) {
        d->pendingRemovalIds.remove(item.id());
    }

    Q_EMIT dataChanged(index(row), index(row));
}

/**
 * 初始化某条消息的timer,
 * 如果已经存在timer，那就重新启动timer，将消息收起的时间延长
 * timer计时结束后，将允许收起的消息收起
 * @param id 消息id
 * @param timeout 超时时间，单位：毫秒
 * @param destroy 在定时结束后，是否删除消息
 */
void NotificationModel::updateNotificationTimer(uint id, int timeout, bool destroy)
{
    QTimer *timer = d->notificationTimers.value(id, nullptr);
    if (!timer) {
        timer = new QTimer();
        timer->setSingleShot(true);
        timer->setProperty("id", id);

        // timer的两种状态:
        // 1.作为弹窗计时器 (计时结束后收起弹窗)
        // 2.作为消息生命周期计时器 (计时结束后删除消息)
        connect(timer, &QTimer::timeout, this, [this, timer] {
            uint id = timer->property("id").toUInt();
            bool destroy = timer->property("destroy").toBool();
            // 删除消息
            if (destroy) {
                //更新actions状态
                notificationExpired(id);
            } else {
                storeNotification(id);
            }
        });

        d->notificationTimers.insert(id, timer);
    }

    timer->stop();
    timer->setProperty("destroy", destroy);
    timer->setInterval(timeout);
    timer->start();
}

/**
 * 定时器结束后，调用该函数
 * @param id
 */
void NotificationModel::storeNotification(uint id)
{
    int idx = findNotificationIndex(id);
    if (idx < 0) {
        d->deleteTimer(id);
        return;
    }

    NotificationItem &item = d->notifications[idx];

    int timeout = item.data.timeout();
    if (timeout > 0) {
        if (timeout > item.data.popupTimeout()) {
            timeout = timeout - item.data.popupTimeout();
        }

        updateNotificationTimer(id, timeout, true);

    } else {
        d->deleteTimer(id);
    }

    if (!(item.flag & NotificationItem::Stored)) {
        item.flag = NotificationItem::Stored;
        Q_EMIT dataChanged(index(idx), index(idx), {NotificationItem::IsStored});
    }
}

void NotificationModel::notificationExpired(uint id)
{
//    d->prepareToDeleteNotification(id);
    int idx = findNotificationIndex(id);
    if (idx < 0) {
        d->deleteTimer(id);
        return;
    }

    NotificationItem &item = d->notifications[idx];
    if (item.flag & NotificationItem::TransientPopup) {
        item.flag = NotificationItem::Stored;
    }

    item.flag |= NotificationItem::Expired;

    for (auto &action : item.actions) {
        action.setEnable(false);
    }

    d->deleteTimer(id);
    Q_EMIT dataChanged(index(idx), index(idx), {NotificationItem::Actions, NotificationItem::IsExpired, NotificationItem::IsStored});
}

void NotificationModel::removeNotifications()
{
    if (d->pendingRemovalIds.isEmpty()) {
        return;
    }

    QVector<int> rows;
    rows.reserve(d->pendingRemovalIds.size());

    for (uint id : d->pendingRemovalIds) {
        int row = findNotificationIndex(id);
        if (row < 0) {
            continue;
        }
        rows.append(row);
    }

    if (rows.isEmpty()) {
        d->pendingRemovalIds.clear();
        return;
    }

    std::sort(rows.begin(), rows.end());

    // 准备删除row数据
    QVector<QPair<int, int>> rangeList;
    QPair<int, int> range {rows[0], rows[0]};

    for (int row : rows) {
        if (row > (range.second + 1)) {
            rangeList.append(range);
            range.first = row;
        }

        range.second = row;
    }

    if (rangeList.isEmpty() || (rangeList.last() != range)) {
        rangeList.append(range);
    }

    int removedRows = 0;
    for (int i = rangeList.count() - 1; i >= 0; --i) {
        range = rangeList[i];
        beginRemoveRows(QModelIndex(), range.first, range.second);
        for (int j = range.second; j >= range.first; --j) {
            // close notification
            NotificationItem item = d->notifications.takeAt(j);
            d->client->closeNotification(item.id(), item.closeReason);
            ++removedRows;
        }
        endRemoveRows();
    }

//    qDebug() << "=removedRows=" << removedRows;
    d->pendingRemovalIds.clear();
}


// ====== !!! Model oh ~ oh ~ oh ~ oh ~ oh ~ ======
int NotificationModel::rowCount(const QModelIndex &parent) const
{
    return d->notifications.count();
}

QVariant NotificationModel::data(const QModelIndex &index, int role) const
{
    if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
        return {};
    }

    const NotificationItem &item = d->notifications[index.row()];
    switch (role) {
        case NotificationItem::Id:
            return item.id();
        case NotificationItem::Display:
            return item.data.display();
        case NotificationItem::AppName:
            return item.data.applicationName();
        case NotificationItem::AppIconName: {
            if (item.data.icon().isEmpty()) {
                return item.data.applicationIconName();
            }
            return item.data.icon();
        }
        case NotificationItem::Icon:
            return item.data.icon();
        case NotificationItem::Summary:
            return item.data.summary();
        case NotificationItem::Body: {
            return item.data.body();
        }
        case NotificationItem::Category:
            return item.data.category();
        case NotificationItem::Image: {
            if (!item.data.image().isNull()) {
                return item.data.image();
            }

            return {};
        }
        case NotificationItem::CreateTime:
            return item.data.createdTime();
        case NotificationItem::Actions: {
            // for qt 5.12
            QVariantList list;
            for (const auto &ac : item.actions) {
                list.append(QVariant::fromValue(ac));
            }
            return list;
//            return QVariant::fromValue(item.actions);
        }
        case NotificationItem::EnableActionIcons:
            return item.data.enableActionIcons();
        case NotificationItem::SoundFile:
            return item.data.soundFile();
        case NotificationItem::SuppressSound:
            return item.data.suppressSound();
        case NotificationItem::Resident:
            return item.data.resident();
        case NotificationItem::Transient:
            return item.data.transient();
        case NotificationItem::Urgency:
            return item.data.urgency();
        case NotificationItem::Timout:
            return item.data.timeout();
        case NotificationItem::NoFold:
            return item.data.noFold();
        case NotificationItem::PopupTimeout:
            return item.data.popupTimeout();
        case NotificationItem::IsStored: {
            return item.flag & NotificationItem::Stored;
        }
        case NotificationItem::IsExpired:
            return item.flag & NotificationItem::Expired;
        case NotificationItem::HasDefaultAction:
            return item.data.hasDefaultAction();
        default:
            break;
    }

    return {};
}

QHash<int, QByteArray> NotificationModel::roleNames() const
{
    return NotificationItem::roles();
}

void NotificationModel::closeNotification(uint id)
{
    int index = findNotificationIndex(id);
    if (index < 0) {
        return;
    }

    d->notifications[index].closeReason = NotificationCloseReason::DismissedByUser;
    d->prepareToDeleteNotification(id);
}

void NotificationModel::execAction(uint id, QString action)
{
    int index = findNotificationIndex(id);
    if (index < 0) {
        return;
    }

    NotificationItem &item = d->notifications[index];
    if (item.flag & NotificationItem::Expired) {
        item.closeReason = NotificationCloseReason::DismissedByUser;
        d->prepareToDeleteNotification(id);
        return;
    }

    if (action.isEmpty()) {
        if (item.data.hasDefaultAction()) {
            action = "default";
        } else {
//            item.closeReason = NotificationCloseReason::Revoked;
//            d->prepareToDeleteNotification(id);
            return;
        }
    }

    d->client->invokeAction(id, action);
    if (!item.data.resident()) {
        item.closeReason = NotificationCloseReason::Revoked;
        d->prepareToDeleteNotification(id);
    }
}

void NotificationModel::clearAll()
{
    beginRemoveRows(QModelIndex(), 0, d->notifications.size() - 1);

    int i = d->notifications.size() - 1;
    for (; i >= 0; --i) {
        d->deleteTimer(d->notifications.at(i).id());
        d->notifications.removeAt(i);
    }

    endRemoveRows();
}

void NotificationModel::removeNotification(uint id)
{
    d->prepareToDeleteNotification(id);
}

void NotificationModel::storePopupNotification(bool isShow)
{
    if (m_sidebarVisable == isShow) return;
    if (isShow) {
        for (int i = 0; i < d->notifications.length(); i++) {
            NotificationItem &item = d->notifications[i];
            if (item.flag == NotificationItem::TransientPopup) {
                item.flag = NotificationItem::Stored;
                Q_EMIT dataChanged(index(i), index(i), {NotificationItem::IsStored});
            }
        }
    }
    m_sidebarVisable = isShow;
}


} // Notification
